//
//  HQCapture4ViewController.m
//  精通Objective-C
//
//  Created by 花强 on 2017/11/7.
//  Copyright © 2017年 花强. All rights reserved.
//

#import "HQCapture4ViewController.h"

@interface HQCapture4ViewController ()

@end

@implementation HQCapture4ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    /*
     第4章 内存管理
     
     恰当的内存管理是正确而高效地开发程序的关键；
         本章详细介绍为OC程序分配和释放内存的途径、OC的内存模型，以及如何编写实现恰当内存管理的程序；
         计算机操作系统为程序分配有限内存，程序应该仅适用其必需的内存；
     
     OC语言及其运行时环境提供了支持应用内存管理的机制；
     
     
     4.1 程序的内存使用情况
     
     计算机内存中存储和使用程序的方式：
         OC可执行程序是由（可执行）代码、初始化和未初始化的程序数据、链接信息、重定位信息、局部数据和动态数据构成的；
         其中：
             程序数据包括静态方式声明的变量和程序常量（即在程序编译时在代码中设置的常数）；
             可执行代码、程序数据以及链接与重定位信息会以静态方式被分配内存，并在程序的声明周期中一直存在；
             局部（自动）数据在语句中声明并且只在该语句中有效，语句执行后，局部数据不会继续存在；
             从语法上来说，OC的复合语句块就是又括号{}封装的语句集合；
     
     自动数据被存储在程序栈中，程序栈通常是在执行程序/线程前就被设定尺寸的内存段；
         栈用于存储局部变量和调用方法/函数的上下文数据；
         上下文数据包括方法的输入参数、返回值，以及调用完方法后继续执行程序的代码地址；
         操作系统会自动管理这些内存；这些数据会获得栈中的内存，而分配给这些数据的内存会在他们失效后被释放；
     
     在运行时，OC会将创建的对象（通过NSObject的alloc方法创建的）存储在动态分配的内存即堆内存中；
         以动态方式创建对象就意味着需要进行内存管理，因为在堆内存中创建的对象永远不会超过其作用范围；
     
     地位地址----------------------->高位地址
     可执行的二进制代码->程序数据->堆->未使用的内存->栈->输入程序的数据
     
     为程序代码分配的内存是在编写程序的时候设置的，因此占用的是系统内存；
     
     OC程序内存管理的必要性：
         一方面，程序的栈尺寸（通常）是在程序启动时确定的，会自动由系统管理；
         另一方面，在OC中对象是在程序执行时动态创建的，不会有系统自动回收；
     
     不进行内存管理和错误的内存管理会导致：
     1）内存泄漏：
         程序没有释放不再使用的对象，就会导致该问题；继续分配内存的话，最终会耗尽系统内存；
     2）悬挂指针：
         程序释放了仍在使用的对象，会导致该问题；如果将来程序尝试访问这些对象，就会出现程序错误；
     
     
     4.2 OC的内存模型
     
     OC的内存管理是通过引用计数实现的；
         引用计数是一种通过对象的唯一引用，确定对象是否正在被使用的技术；
         如果对象的引用计数降到了0，对象就会被视为不再有用，而且运行时系统也会释放它的内存；
     
     苹果OC开发环境提供了两种内存管理机制：
         手动管理（MRR）（也就是我们通常说的MRC，C是count的意思，是相对ARC来说的）；
         自动引用计数（ARC）；
     
     
     4.3 手动管理
     
     手动管理（MRR）：是一种建立在对象所有权概念上的内存管理机制；
         只要对象所有者还存在，对象就不会被OC运行时环境释放；
         可以通过编写代码精确的管理对象的回收利用；
     
     我们先来理解下访问和使用对象的方式，以及访问对象与对象所有权之间的差别；
     
     4.3.1 对象引用和对象所有权
     
     OC对象是通过指向OC对象内存地址的变量，以间接方式访问的；
         这种变量就是C语言中的指针；
         指针的应用范围很广，包括OC的基本数据类型与C语言的数据类型；
         但，对象指针专门用于OC对象的交互操作；
     
     对象指针实现了OC对象的访问功能；
         但是，它们本身并不能管理所有权；
         比如:
             A * a = [[A alloc] init];
             A * b = a;
         声明一个对象指针 ，指向另一个同类型的对象指针；我们虽然改变了指针的指向，但是并未设置原始对象的所有权；
             这会导致，如果a被释放了，b就指向了一个不合法的对象；
     
     要以MRR方式管理对象生命周期：即保留和释放，在编写代码时需要遵守一系列内存管理规则；
     
     4.3.2 内存管理基本原则
     
     要正确使用MRR，编写代码时必须在获取对象所有权与释放对象所有权之间进行平衡；
     因此需遵循：
     1）为创建的所有对象设置所有权：
         应使用名称以alloc、new、copy或mutableCopy开头的方法创建OC对象；
         还应通过向块发送copy消息，以动态的方式创建OC块对象；
     
     2）应使用retain方法获取对象（你尚未拥有）的所有权：
         使用NSObject的retain方法可以获得对象的所有权；
         使用retain可以获取你想要长时间使用的对象的所有权，这样做通常可以将其存储为属性值，并防止其他操作无意中释放该对象；
     
     3）当不再使用某个对象时，必须放弃其所有权：
         使用NSObject类的release和autorelease方法可以释放对象的所有权；
         使用autorelease方法可以在当前自动释放代码块的末尾，放弃对象的所有权；
         如果对象的引用计数为0，这两种方法都会对对象执行dealloc方法；
     
     4）不能放弃不归你所有的对象的所有权：
         这样做会导致过早滴释放这些对象，如果程序尝试方位已经被释放的对象，就会出错；
     
     注意，所有权是对象指针变量相对于对象来讲的，对象一直都在，获取和释放的是对象指针变量对 对象的所有权；对象的所有权（用计数表示）清0时，相应的内存空间也会被运行时系统释放；
     
     释放操作：
     1）释放内存：
         当对象的引用计数为0时，运行时系统会通过NSObject类的dealloc方法释放掉该对象使用的内存；（注意理解这句话）
         该方法还提供了放弃子类对象所有权的框架：
             -(void)dealloc{
                 [... release];
                 ...
                 [super dealloc];
             }
         你编写的类（通常都是NSObject类的子类）都应该重写dealloc方法，调用它们实例变量的release方法，然后调用它们父类的dealloc方法；
         通过这种方式可以是你编写的类，遵循类的层次结构以适当的方式放弃对象的所有权；
     
     2）通过autorelease方法延迟释放操作：
         通过NSObject类的autorelease方法可以在自动释放池代码块的末尾，调用对象中的方法；
         自动释放代码块 提供了在将来某个时间放弃对象所有权的机制，因而无须编写调用对象中release方法的具体代码，并能避免对象立刻被释放的情况；
     
         使用@autorelease指令可以定义自动释放池代码块：
             @autorelease{
                 //创建自动释放对象的代码
                 ...
             }
         应该总是将创建自动释放对象的代码放在自动释放池代码块中；
             否则他们将无法收到release消息，从而导致内存泄漏；
 
         用于创建iOS和Mac OS X应用的苹果应用框架，尤其是AppKit和UIKit，能够自动提供自动释放代码块；
         需要手动编写自动释放代码块的情况有以下几种：
         （1）你编写的程序不是以苹果UI框架为基础的，如命令行工具；
         （2）你实现的逻辑中含有创建很多临时对象的循环；
             为了降低应用占用内存的最大值，你在该循环中添加自动释放池代码块，以便在下一次迭代前处置这些对象；
         （3）你编写的应用派生出来一个或多个辅助线程；
             你必须在执行辅助线程的位置添加自己编写的自动释放池代码块，否则你的应用就会内存泄漏；
     
     举个例子：
         你可以向这样写：
         @autorelease{
             A * a = [[[A alloc] init] autorelease];
             ...
         }
     注意：
         上述对象在创建并初始化之后，会立即收到autorelease消息，这通常由一条复合语句实现；
         这种设计可以确保所有通过autorelease消息创建的对象都会在程序结束前、在自动释放代码块的末尾被释放；
     
     
     4.3.3 使用MRR
     
     示例：
         NSString * string1 = [NSString new];//new为创建的对象设置所有权
         NSString * string2 = string1;   //对象指针变量赋值
         [string2 retain];               //对象指针变量使用retain获取对象所有权
     
         AClass * a = [[AClass alloc] init];//alloc为创建的对象设置所有权
     
         [string1 release];
         [string2 release];              //对象指针变量使用release放弃对象所有权
         [a release];
     
     以上示例展示了 以动态方式创建对象时，调动初始化和释放方式的顺序；
         所有对象的创建/保留和释放消息都达到了平衡；
     
     Xcode的Product菜单的Analyze选项：该工具会分析程序，检测潜在的内存泄漏，悬挂指针等问题；（Commend + shift + B）
     当然也可以用Xcode Instrument工具进行内存使用情况的分析；

     
     4.4 使用自动引用计数
     
     在使用MRR内存管理方式时，你会亲自管理程序中对象回收工作；
         好处是可以精细地控制内存的使用情况；
         坏处是大幅度增加了开发人员的负担；
     
     因此，手动引用计数更容易出错，会导致程序泄漏内存或崩溃；最好能使用工具完成这类基本任务；
     
     自动引用计数（ARC）：
         是一种功能强大的内存管理工具，是这类任务自动化；
         ARC使用引用计数的模式与MRR使用的相同，但是由编译器管理回收对象的工作；
     
     具体的：
         在编译程序时，编译器会分析源代码，确定以动态方式创建的对象的回收需求，然后在已编译代码的必要位置自动插入retain和release消息；
         APR还可以（潜在地）提升应用的性能和消除内存管理错误（如错误释放仍在使用的对象、保留不再使用的对象）；
         此外，和垃圾回收机制相比，ARC更可靠（保留和释放语句是在编译时插入的），并且不会为实现垃圾回收机制而在程序执行过程中引入暂停操作；
     
     ARC可以为OC对象和块提供自动内存管理功能；
         注意，ARC无法自动处理循环引用；
         为此，OC提供了弱引用功能，在必要时通过该功能你可以手动解消循环引用；
     
     4.4.1 使用ARC的规则和约定
     
     与MRR相比，ARC可大幅度简化内存管理工作；
     使用ARC时应遵守规则：
     1）不能手动编写发送retain、retainCount、release、autorelease和dealloc消息的代码；
         ARC不允许手动方式控制对象的回收工作；
         如果需要管理实例变量之外的资源，可以手动编写dealloc方法（比如注销通知）；
         ARC在您没有在类中编写dealloc方法的情况下，会自动创建该方法，释放其中的对象，并调用父类的dealloc方法；
         在类中编写dealloc方法的情况下，是不能再调用[super dealloc]的；
     
     2）不能直接进行id和（void *）类型的互转；
         ARC只能管理OC对象和块，因此编译器只能处理能够识别出其类型的对象；
         因为（void *）类型的指针是能够转换为任何指针类型（包括OC中没有的指针类型）的通用指针；所以必须设置这个限制；
         当使用Foundation框架对象和Core Foundation对象（一种提供C语言API的Apple软件库）互相转换类型时，此种情况较常见，会有相应的API供在ARC和非ARC环境之间进行变量所有权的转移；
     
     3）需要使用自动释放池代码块执行由ARC管理的自动释放操作；
     4）不能调用Foundation框架函数NSAllocateObject和NSDeallocateObject；
         这两个函数提供了在指定内存区域为对象分配和释放内存的功能；因为OC不再支持内存区，所以无法使用它们；
     
     5）无法使用C结构中的对象指针；
         ARC不能为动态分配的C结构执行内存管理操作，因为编译器无法判断何时应插入必需的retain和release消息；
     
     6）不能使用内存区（NSZone）；如前所述，OC不再支持内存区；
     7）为了与非ARC代码协作，不能创建以copy开头的方法和自动声明属性（除非明确定义了其他读取器）；
     8）默认情况下，ARC并非异常安全的；
         无法释放异常失效的__strong变量；
         无法在完整表达式抛出异常时，将位于该完整表达式末尾的对象释放；
         使用编译器选项-fobjc-arc-exceptions可以启动处理ARC代码异常的功能；除非编译器解决了该异常，否则当弱引用异常失效时，ARC肯定会释放弱引用变量；
     
     上边都是一些规则，有的比较难懂，在不断学习和成长中，慢慢体会吧；
     
     4.4.2 ARC的声明周期限定符
     
     OC提供了一系列专用的ARC限定符，使用它们可以声明常规变量和属性的生命周期；
     
     应用于常规变量的限定符：
     1)__strong:
         表明使用任何alloc/init消息创建的对象都会在其作用范围内被保留；
         “作用范围”通常是指变量声明所在花括号对的内部（如方法、循环、条件语句块）；
         这是常规变量的默认设置；
     
     2)__weak:
         表明对象随时都可以被释放；
         只有当对象拥有其他强引用时，该标记才会有用处；（因为weak是弱引用）
         对象被释放之后，待__weak限定符的变量会被设置为nil；
     
     3)__unsafe_unretained:
         与__weak限定符类似，但是在对象被释放后，指针不会被置为nil，而是处于悬空状态（不再指向合法对象）；
         所以是不安全的 也不会获得对象的所有权的；
     
     4)__autoreleasing:
         不要将该限定符与调用对象中的autorelease方法搞混了，这个限定符用于通过引用传递对象；
     
     使用ARC生命周期限定符声明OC的对象变量时，语法如下：
         类名 * 限定符 变量名；
         如果没有设置限定符，系统会使用默认值__strong；
     
     应用于属性的ARC生命周期限定符：
     1)strong:
         等同于retain特性，获取对象所有权；
     
     2)weak:
         类似于assign特性，无数次被问到过assign和weak的区别，两者都是简单赋值操作，区别在于，weak特性限定的，如果引用对象被释放了，其实例变量会被设置为nil；
     
     ARC中，strong是对象型属性默认的所有权特性；
     （后续会详细介绍一下属性的特性）
     
     4.4.3 使用ARC
     
     ARC中类的init方法（无对象变量赋值）和MRR中的一样；
     但dealloc方法不同，ARC中的不调用父类的dealloc方法，因为这是由ARC自动执行的操作；
     
     给出一个例子：
     MRR:
     -initWithName:(NSString *)name{
         if(self = [super init]){
             _name = name;
             [_name retain];
         }
         return self;
     }
     -(void)dealloc{
         [_name release];
         [super dealloc];
     }
     
     ARC:
     -initWithName:(NSString *)name{
         if(self = [super init]){
             _name = name;
         }
         return self;
     }
     -(void)dealloc{
     
     }
     
     通过ARC内存管理方式创建的对象，不再使用时（比如置为nil，或作用范围结束），ARC会自动释放它们；
     ARC还会管理必需的依存对象，避免出现正在使用对象的情况；
     
     4.4.4 避免循环引用
     
     OC的引用计数模型是通过获取对象所有权（通过retain消息），以及再不使用对象后释放对象的所有权（通过release）实现的；
 
     ARC自动化了该过程；
         它会根据需要在代码中自动插入retain/release/autorelease消息；
     
     然而，如果两个对象直接或间接相互引用，就会导致循环引用问题；
         比如，类A的对象a拥有一个类B的实例，如果类B的这个实例对象也拥有类A的对象实例，就会在这两个对象之间造成循环引用；
         两个对象将永远不会被释放；
     
     解决方式是使用弱引用：
         弱引用是一种非所有权引用，被弱引用的对象不属于引用它的对象，从而消除循环引用；
     
     OC约定：
         父对象强引用其所有子对象；
         子对象弱引用其父对象；
     
     因此上述的问题可以这样修改：
     @Interface A:NSObject{
     @public B * b;
     }
     @Interface B:NSObject{
     @public A * __weak a;
     }
     
     当B类对象被释放时，变量a也会被置为nil；避免了循环引用；
     
     
     4.5 小结
     
     本章介绍了：
     OC中的内存管理，包括：
         OC内存模型；
         为OC程序分配和释放内存的方式；
         以及两种OC内存管理机制的用法；
     
     要点：
     1）在运行时，OC程序创建的对象（通过NSObject类的alloc方法）会以动态方式存储在预先分配的内存区域中，这片内存区域称为堆内存；
         以动态方式创建对象意味着需要管理内存，因为在堆内存中创建的对象会一直使用该区域中的内存；
         不进行内存管理或者采用错误的内存管理方式，通常会导致内存泄漏和悬挂指针问题；
     
     2）OC的内存管理是使用引用计数实现的；
         该技术通过对象的唯一引用判断对象是否正在被使用；
         如果某个对象的引用计数变为0，那么就会被认为不再有用；运行时系统会释放它占用的内存；
     
     3）OC开发环境有两种内存管理机制：手动管理（MRR）和自动引用计数（ARC）；
     4）在使用MRR内存管理方式时，需要编写确切的代码，管理对象的生命周期、获取对象（你创建的或需要使用的）所有权和释放对象（不再需要的）所有权；
     
     5）ARC使用的引用计数模型与MRR使用的引用计数模型相同，不同的是ARC通过编译器自动管理对象的生命周期；
         在编译程序时，编译器会分析源代码，确定以动态方式创建的对象的生命周期，然后在已编译的代码中自动插入必需的retain和release消息；
     
     6）ARC中增加了新的对象生命周期限定符，使用这些限定符可以确切地声明对象变量和属性的生命周期，还可以实现弱引用，避免出现循环引用；
     
     精巧的OC程序仅会占用它所必需的内存，不会使内存泄漏和尝试访问已经失效的对象；
     
     接下来做好准备，迎接下一章——预处理器。

     */
    
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

/*
#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

@end
